package ss.pjname.base.common.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 字符串操作工具类
 * Created by 王成 on 2017/12/21.
 */
@Slf4j
public class StringUtil {
    private static final Pattern XSS_PATTERN = Pattern.compile("<[\\s\\x00]*SCRIPT|%3C[+\\s\\x00]*SCRIPT|SELECT\\s|INSERT\\s|DELETE\\s|UPDATE\\s|DROP\\s|<!--|-->|<[\\s\\x00]*IMG|<[\\s\\x00]*HEAD|<[\\s\\x00]*BOY|<[\\s\\x00]*DIV|<[\\s\\x00]*TABLE|<[\\s\\x00]*FRAME|<[\\s\\x00]*IFRAME|<[\\s\\x00]*FRAMESET|<[\\s\\x00]*NOFRAME|<[\\s\\x00]*PLAINTEXT|<[\\s\\x00]*LINK|<[\\s\\x00]*MAP|<[\\s\\x00]*BGSOUND|<[\\s\\x00]*FORM|<[\\s\\x00]*INPUT|<[\\s\\x00]*BODY|<[\\s\\x00]*SELECT|<[\\s\\x00]*OPTION|<[\\s\\x00]*TEXTAREA|<[\\s\\x00]*APPLET|<[\\s\\x00]*OBJECT|<[\\s\\x00]*EMBED|<[\\s\\x00]*NOSCRIPT|<[\\s\\x00]*STYLE|%3C[+\\s\\x00]*IMG|%3C[+\\s\\x00]*HEAD|%3C[+\\s\\x00]*BOY|%3C[+\\s\\x00]*DIV|%3C[+\\s\\x00]*TABLE|%3C[+\\s\\x00]*FRAME|%3C[+\\s\\x00]*IFRAME|%3C[+\\s\\x00]*FRAMESET|%3C[+\\s\\x00]*NOFRAME|%3C[+\\s\\x00]*PLAINTEXT|%3C[+\\s\\x00]*LINK|%3C[+\\s\\x00]*MAP|%3C[+\\s\\x00]*BGSOUND|%3C[+\\s\\x00]*FORM|%3C[+\\s\\x00]*INPUT|%3C[+\\s\\x00]*SELECT|%3C[+\\s\\x00]*OPTION|%3C[+\\s\\x00]*TEXTAREA|%3C[+\\s\\x00]*APPLET|%3C[+\\s\\x00]*OBJECT|%3C[+\\s\\x00]*EMBED|%3C[+\\s\\x00]*NOSCRIPT|%3C[+\\s\\x00]*STYLE|ALERT[\\s\\x00]*\\(|PROMPT[\\s\\x00]*\\(|CONFIRM[\\s\\x00]*\\(|((SELECT|FROM|INSERT|DELETE|V$VERSION|WHERE|ROWNUM|CASE|BANNER|THEN)([\\s]*)/\\*\\*/)|'\\s*OR.+=");
    private static final Pattern SQL_PATTERN = Pattern.compile("\\b(and|exec|insert|select|drop|grant|alter|delete|update|count|chr|mid|master|truncate|char|declare|or)\\b|([;+'%])");

    /**
     * 字符串前补0
     *
     * @param str    原字符串
     * @param length 目标字符串长度
     * @return 目标字符串
     */
    public static String leftFillZero(String str, int length) {
        if (str.length() >= length) {
            return str.substring(0, length);
        }
        String format = "%0" + (length - str.length()) + "d";
        return String.format(format, 0) + str;
    }

    /**
     * 字符串前补字符
     *
     * @param str     原字符串
     * @param length  目标字符串长度
     * @param padchar 前补字符
     * @return 目标字符串
     */
    public static String leftFillChar(String str, int length, String padchar) {
        StringBuilder strBuilder = new StringBuilder(str);
        while (strBuilder.length() < length) {
            strBuilder.insert(0, padchar);
        }
        str = strBuilder.toString();
        return str;
    }

    /**
     * 字符串后补0
     *
     * @param str    原字符串
     * @param length 目标字符串长度
     * @return 目标字符串
     */
    public static String rightFillZero(String str, int length) {
        if (str.length() >= length) {
            return str.substring(0, length);
        }
        String format = "%0" + (length - str.length()) + "d";
        return str + String.format(format, 0);
    }

    /**
     * 字符串后补字符
     *
     * @param str     原字符串
     * @param length  目标字符串长度
     * @param padchar 前补字符
     * @return 目标字符串
     */
    public static String rightFillChar(String str, int length, String padchar) {
        StringBuilder strBuilder = new StringBuilder(str);
        while (strBuilder.length() < length) {
            strBuilder.append(padchar);
        }
        str = strBuilder.toString();
        return str;
    }

    /**
     * 字符串压缩
     *
     * @param bytes 原字符串
     * @return 目标字符串
     */
    public static byte[] compress(byte[] bytes) {
        ByteArrayOutputStream buff = new ByteArrayOutputStream();
        int hb, lb;
        for (int i = 0; i < bytes.length; i = i + 2) {
            hb = bytes[i] & 0xff;
            if (i == (bytes.length - 1)) {
                lb = 0;
            } else {
                lb = bytes[i + 1] & 0xff;
            }
            if (hb > 0x39) {
                hb -= 0x37;
            } else {
                hb -= 0x30;
            }
            if (lb > 0x39) {
                lb -= 0x37;
            } else {
                lb -= 0x30;
            }
            buff.write(((hb << 4) | (lb & 0x0f)));
        }
        return buff.toByteArray();
    }

    /**
     * 字符串解压缩
     *
     * @param bytes 原字符串
     * @return 目标字符串
     */
    public static String uncompress(byte[] bytes) {
        StringBuilder destStrBuff = new StringBuilder();
        for (byte aByte : bytes) {
            destStrBuff.append(Integer.toHexString(aByte & 0xff));
        }
        return destStrBuff.toString();
    }

    /**
     * 将十六进制字符串转换成二进制字节数组
     *
     * @param hexString 16进制字符串
     * @return 二进制字节数组
     */
    public static byte[] hexStringToBinary(String hexString) {
        if (null == hexString || hexString.isEmpty()) {
            return new byte[0];
        }
        return DatatypeConverter.parseHexBinary(hexString);
    }

    /**
     * 将二进制字节数组转换成16进制字符串
     *
     * @param byteValue 二进制字节数组
     * @return 16进制字符串
     */
    public static String binaryToHexString(byte[] byteValue) {
        if (null == byteValue || byteValue.length == 0) {
            return null;
        }
        return DatatypeConverter.printHexBinary(byteValue);
    }

    /**
     * 将Base64编码字符串转换成二进制字节数组
     *
     * @param base64String Base64字符串
     * @return 二进制字节编码
     */
    public static byte[] base64StringToBinary(String base64String) {
        if (null == base64String || base64String.isEmpty()) {
            return new byte[0];
        }
        return DatatypeConverter.parseBase64Binary(base64String);
    }

    /**
     * 将二进制字节数组转换成Base64字符串
     *
     * @param byteValue 二进制字节数组
     * @return Base64字符串
     */
    public static String binaryToBase64String(byte[] byteValue) {
        if (null == byteValue || byteValue.length == 0) {
            return null;
        }
        return DatatypeConverter.printBase64Binary(byteValue);
    }

    /**
     * 取中文字符串的子串
     *
     * @param src         中文字符串
     * @param len         截取长度
     * @param charsetName 字符集类型
     * @return 中文字符串子串
     * @throws UnsupportedEncodingException 操作异常
     */
    public static String substring(String src, int len, String charsetName) throws UnsupportedEncodingException {
        if (src.getBytes(charsetName).length > len) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(len);
            int dstLen = 0;
            String dstStr = null;
            char[] srcArray = src.toCharArray();
            for (char c : srcArray) {
                byte[] charBytes = String.valueOf(c).getBytes(charsetName);
                if (dstLen < len && dstLen + charBytes.length < len) {
                    byteBuffer.put(charBytes);
                    dstLen = dstLen + charBytes.length;
                } else {
                    byte[] dst = new byte[dstLen];
                    byte[] buff = byteBuffer.array();
                    System.arraycopy(buff, 0, dst, 0, Math.min(buff.length, dst.length));
                    dstStr = new String(dst, charsetName);
                    break;
                }
            }
            return dstStr;
        } else {
            return src;
        }
    }

    /**
     * 截取字符串中两个字符之间的字符串
     *
     * @param srcStr   需要截取的字符串
     * @param strStart 截取开始字符串
     * @param strEnd   截取截止字符串
     * @return 起始与截止之间的字符串，返回null表示没有截取到
     */
    public static String subIntercept(String srcStr, String strStart, String strEnd) {
        /* 找出指定的2个字符在 该字符串里面的 位置 */
        int strStartIndex = srcStr.indexOf(strStart);
        int strEndIndex = srcStr.indexOf(strEnd);
        /* 若起始字符串与截止字符串相同，则截止字符串索引从开始字符串后面开始查找 */
        if (strStart.equals(strEnd)) {
            String tmpStr = srcStr.substring(strStartIndex + strStart.length());
            int offset = tmpStr.indexOf(strEnd);
            strEndIndex = strStartIndex + strStart.length() + offset;
        }
        /* index 为负数 即表示该字符串中没有该字符 */
        if (strStartIndex < 0 || strEndIndex < 0) {
            return null;
        }
        /* 截取有效字符 */
        String result = srcStr.substring(strStartIndex, strEndIndex);
        return result.substring(strStart.length());
    }

    /**
     * 将带下划线的字符串转换成驼峰命名的字符串（如将HELLO_WORLD转换成helloWord）
     *
     * @param underScoreString 带下划线的字符串
     * @return 驼峰命名的字符串
     */
    public static String underScoreToCamelString(String underScoreString) {
        StringBuilder result = new StringBuilder();
        /* 快速检查字符串是否为空，为空直接返回空字符串 */
        if (null == underScoreString || underScoreString.isEmpty()) {
            return "";
        } else if (!underScoreString.contains("_")) {
            /* 若字符串不含下划线，则将字符串全部转换成小写返回 */
            return underScoreString.toLowerCase();
        }
        /* 用下划线将原始字符串分割 */
        String[] camels = underScoreString.split("_");
        for (String camel : camels) {
            /* 跳过原始字符串中开头、结尾的下换线或双重下划线 */
            if (camel.isEmpty()) {
                continue;
            }
            /* 处理真正的驼峰片段 */
            if (result.length() == 0) {
                /* 第一个驼峰片段，全部字母都小写 */
                result.append(camel.toLowerCase());
            } else {
                /* 其他的驼峰片段，首字母大写 */
                result.append(camel.substring(0, 1).toUpperCase());
                result.append(camel.substring(1).toLowerCase());
            }
        }
        return result.toString();
    }

    /**
     * 将驼峰命名的字符串转换成带下划线的大写字符串（如将helloWord转换成HELLO_WORLD）
     *
     * @param camelString 驼峰命名的字符串
     * @return 带下划线的全大写字符串
     */
    public static String camelToUnderScoreString(String camelString) {
        StringBuilder result = new StringBuilder();
        if (null != camelString && camelString.length() > 0) {
            /* 将第一个字符处理成大写 */
            result.append(camelString.substring(0, 1).toUpperCase());
            /* 循环处理其余字符 */
            for (int i = 1; i < camelString.length(); i++) {
                String str = camelString.substring(i, i + 1);
                /* 在大写字母前添加下划线 */
                if (str.equals(str.toUpperCase()) && !Character.isDigit(str.charAt(0))) {
                    result.append("_");
                }
                /* 其他字符直接转成大写 */
                result.append(str.toUpperCase());
            }
        }
        return result.toString();
    }

    /**
     * 校验字符串是否为空或者为NULL
     *
     * @param checkString 校验的字符串
     * @return true-字符串为空或者为NULL，false-字符串非空
     */
    public static boolean isEmptyOrNull(String checkString) {
        return null == checkString || checkString.isEmpty() || checkString.trim().isEmpty();
    }

    /**
     * 判断字符串是否为纯数字
     *
     * @param checkString 待校验的字符串
     * @return true-是,false-否
     */
    public static boolean isPureDigital(String checkString) {
        for (int i = 0; i < checkString.length(); i++) {
            if (!Character.isDigit(checkString.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 过滤ASCII码（允许的字符范围为：ASCII 码值大于等于 32 并且小于等于 126）
     *
     * @param str 待过滤字符串
     * @return 过滤后的字符串
     */
    public static String filterAsciiString(String str) {
        char[] chars = str.toCharArray();
        StringBuilder stringBuilder = new StringBuilder();
        for (char aChar : chars) {
            if ((int) aChar >= 32 && (int) aChar <= 126) {
                stringBuilder.append(aChar);
            }
        }
        return stringBuilder.toString();
    }

    /**
     * Http的FormData转换JSON字符串
     *
     * @param formData http的表单数据
     * @return JSON字符串
     */
    public static String parseHttpFormDataToJsonString(String formData) {
        String[] tmpArray = formData.split("\r\n");
        Map<String, String> fieldMap = new HashMap<>();
        String key = null;
        String value = null;
        boolean flag = false;
        for (String item : tmpArray) {
            if (item.isEmpty() || item.startsWith("-")) {
                continue;
            } else if (item.startsWith("Content-Disposition: form-data: name=")) {
                key = subIntercept(item, "\"", "\"");
            } else {
                value = item;
                flag = true;
            }
            if (flag && null != key) {
                fieldMap.put(key, value);
                key = null;
                value = null;
                flag = false;
            }
        }
        return JSON.toJSONString(fieldMap);
    }

    /**
     * Http的Get请求参数转换成JSON字符串
     *
     * @param paramterData Get请求参数
     * @return JSON字符串
     */
    public static String parseHttpGetParamToJsonString(String paramterData) throws UnsupportedEncodingException {
        paramterData = URLDecoder.decode(paramterData, "UTF-8");
        String[] tempArray = paramterData.split("&");
        Map<String, String> fieldMap = new HashMap<>();
        for (String item : tempArray) {
            int index = item.indexOf("=");
            String key = item.substring(0, index).trim();
            String value = item.substring(index + 1).trim();
            if (key.startsWith("/?")) {
                fieldMap.put(key.substring(2), value);
            } else {
                fieldMap.put(key, value);
            }
        }
        return JSON.toJSONString(fieldMap);
    }

    /**
     * 校验是否含有sql注入
     *
     * @param object 校验的对象
     * @return true-有,false-否
     */
    public static boolean containsSqlInjection(Object object) {
        try {
            List<Field> fieldList = new ArrayList<>();
            Class<?> clasz = object.getClass();
            while (null != clasz) {
                fieldList.addAll(Arrays.asList(clasz.getDeclaredFields()));
                clasz = clasz.getSuperclass();
            }
            for (Field field : fieldList) {
                field.setAccessible(true);
                Type type = field.getGenericType();
                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    if (BeanUtil.isBaseDataType(parameterizedType.getActualTypeArguments()[0])) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            Matcher matcher = SQL_PATTERN.matcher(fieldNameValue.toLowerCase());
                            if (matcher.find()) {
                                return true;
                            }
                        }
                    } else {
                        List listObject = (List) field.get(object);
                        for (Object obj : listObject) {
                            boolean flag = containsXssInjection(obj);
                            if (flag) {
                                return true;
                            }
                        }
                    }
                } else {
                    if (BeanUtil.isBaseDataType(type)) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            Matcher matcher = SQL_PATTERN.matcher(fieldNameValue.toLowerCase());
                            if (matcher.find()) {
                                return true;
                            }
                        }
                    } else {
                        boolean flag = containsXssInjection(field.get(object));
                        if (flag) {
                            return true;
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("异常信息:", e);
        }
        return false;
    }

    /**
     * 校验是否含有XSS特殊字符
     *
     * @param object 校验的对象
     * @return true-有,false-否
     */
    public static boolean containsXssInjection(Object object) {
        try {
            if (null == object) {
                return false;
            }
            List<Field> fieldList = new ArrayList<>();
            Class<?> clasz = object.getClass();
            while (null != clasz) {
                fieldList.addAll(Arrays.asList(clasz.getDeclaredFields()));
                clasz = clasz.getSuperclass();
            }
            for (Field field : fieldList) {
                field.setAccessible(true);
                Type type = field.getGenericType();
                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    if (BeanUtil.isBaseDataType(parameterizedType.getActualTypeArguments()[0])) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            Matcher matcher = XSS_PATTERN.matcher(fieldNameValue.toUpperCase());
                            if (matcher.find()) {
                                return true;
                            }
                        }
                    } else {
                        List listObject = (List) field.get(object);
                        if (null != listObject && !listObject.isEmpty()) {
                            for (Object obj : listObject) {
                                boolean flag = containsXssInjection(obj);
                                if (flag) {
                                    return true;
                                }
                            }
                        }
                    }
                } else {
                    if (BeanUtil.isBaseDataType(type)) {
                        if (null != field.get(object)) {
                            String fieldNameValue = String.valueOf(field.get(object));
                            Matcher matcher = XSS_PATTERN.matcher(fieldNameValue.toUpperCase());
                            if (matcher.find()) {
                                return true;
                            }
                        }
                    } else {
                        boolean flag = containsXssInjection(field.get(object));
                        if (flag) {
                            return true;
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("异常信息:", e);
        }
        return false;
    }

    /**
     * 将还分隔符&的kv字符串转换成Map对象
     *
     * @param kvString KV字符串
     * @return MAP对象
     */
    public static Map<String, Object> parseKvString(String kvString) {
        Map<String, Object> map = new HashMap<String, Object>();
        int len = kvString.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        if (len > 0) {
            for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
                curChar = kvString.charAt(i);// 取当前字符
                if (isKey) {// 如果当前生成的是key
                    if (curChar == '=') {// 如果读取到=分隔符
                        key = temp.toString();
                        temp.setLength(0);
                        isKey = false;
                    } else {
                        temp.append(curChar);
                    }
                } else {// 如果当前生成的是value
                    if (isOpen) {
                        if (curChar == openName) {
                            isOpen = false;
                        }
                    } else {//如果没开启嵌套
                        if (curChar == '{') {//如果碰到，就开启嵌套
                            isOpen = true;
                            openName = '}';
                        }
                        if (curChar == '[') {
                            isOpen = true;
                            openName = ']';
                        }
                    }
                    if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                        putKeyValueToMapObj(temp, isKey, key, map);
                        temp.setLength(0);
                        isKey = true;
                    } else {
                        temp.append(curChar);
                    }
                }
            }
            putKeyValueToMapObj(temp, isKey, key, map);
        }
        return map;
    }

    /**
     * 将KEY及VALUE值设置对Map对象
     *
     * @param temp  KEY对应的VALUE值
     * @param isKey 是否为KEY
     * @param key   KEY
     * @param map   MAP对象
     */
    private static void putKeyValueToMapObj(StringBuilder temp, boolean isKey, String key, Map<String, Object> map) {
        if (isKey) {
            key = temp.toString();
            if (key.length() == 0) {
                throw new RuntimeException("String format illegal");
            }
            map.put(key, "");
        } else {
            if (key.length() == 0) {
                throw new RuntimeException("String format illegal");
            }
            map.put(key, temp.toString());
        }
    }

    /**
     * 对JSON字符串按照key进行排序
     *
     * @param jsonString 待排序的字符串
     * @return 排序后的map对象
     */
    public static TreeMap<String, Object> jsonStringKeySort(String jsonString) {
        /* Step1：先对JSON字符串的第一层节点进行排序 */
        TreeMap<String, Object> treeMap = JSON.parseObject(jsonString, TreeMap.class);
        /* Step2：递归判断每个节点值，再对JSON对象及JSONArray进行排序 */
        for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof JSONObject) {
                TreeMap<String, Object> jsonObjectreeMap = jsonStringKeySort(JSON.toJSONString(value));
                treeMap.put(key, JSON.toJSONString(jsonObjectreeMap));
            } else if (value instanceof JSONArray) {
                JSONArray sortJsonArray = new JSONArray();
                JSONArray jsonArray = JSON.parseArray(JSON.toJSONString(value));
                for (int i = 0; i < jsonArray.size(); i++) {
                    TreeMap<String, Object> jsonObjectreeMap = jsonStringKeySort(jsonArray.getJSONObject(i).toJSONString());
                    sortJsonArray.add(jsonObjectreeMap);
                }
                treeMap.put(key, sortJsonArray);
            }
        }
        return treeMap;
    }

    /**
     * 异常堆栈信息转换成字符串
     *
     * @param e 异常信息
     * @return 异常字符串
     */
    public static String exceptionToString(Exception e) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        e.printStackTrace(new PrintWriter(byteArrayOutputStream, true));
        return byteArrayOutputStream.toString();
    }
}
